package com.ekalenchuk.module.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class GenericHibernateDao<T, ID extends Serializable> implements GenericDao<T, ID> {
	private Class<T> persistentClass;

	@SuppressWarnings({ "unchecked" })
	public GenericHibernateDao() {
		persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	@SuppressWarnings({ "unchecked" })
	public T load(ID id, boolean lock) {
		final T entity;
		if (lock) {
			entity = (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
		} else {
			entity = (T) getSession().load(getPersistentClass(), id);
		}
		return entity;
	}

	protected Criteria createCriteria() {
		return getSession().createCriteria(getPersistentClass());
	}

	@SuppressWarnings("unchecked")
	public void setSession(Session s) {
		// session = s;
	}

	private Class<T> getPersistentClass() {
		return persistentClass;
	}

	@SuppressWarnings("unchecked")
	public T findById(ID id, boolean lock) {
		final T entity;
		if (lock) {
			entity = (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
		} else {
			entity = (T) getSession().load(getPersistentClass(), id);
		}
		return entity;
	}

	@SuppressWarnings("unchecked")
	public T getById(ID id, boolean lock) {
		final T entity;
		if (lock) {
			entity = (T) getSession().get(getPersistentClass(), id, LockMode.UPGRADE);
		} else {
			entity = (T) getSession().get(getPersistentClass(), id);
		}
		return entity;
	}

	@SuppressWarnings("unchecked")
	public T findById(ID id) {
		return findById(id, false);
	}

	@SuppressWarnings("unchecked")
	public T getById(ID id) {
		return getById(id, false);
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		return findByCriteria();
	}

	@SuppressWarnings("unchecked")
	public List<T> findByExample(T exampleInstance, String... excludeProperty) {
		final Criteria crit = getSession().createCriteria(getPersistentClass());
		final Example example = Example.create(exampleInstance);
		for (String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		crit.add(example);
		return crit.list();
	}

	@SuppressWarnings("unchecked")
	public T save(T entity) {
		getSession().saveOrUpdate(entity);
		return entity;
	}

	@SuppressWarnings("unchecked")
	public void makeTransient(T entity) {
		getSession().delete(entity);
	}

	@SuppressWarnings("unchecked")
	public void flush() {
		getSession().flush();
	}

	@SuppressWarnings("unchecked")
	public void clear() {
		getSession().clear();
	}

	@SuppressWarnings("unchecked")
	protected List<T> findByCriteria(Criterion... criterion) {
		final Criteria crit = getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterion) {
			if (c == null) {
				continue;
			}
			crit.add(c);
		}
		return crit.list();
	}

	@SuppressWarnings({ "unchecked" })
	protected T findUniqueByCriteria(Criterion... criterion) {
		final Criteria crit = getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterion) {
			if (c == null) {
				continue;
			}
			crit.add(c);
		}
		return (T) crit.uniqueResult();
	}

	@SuppressWarnings({ "unchecked" })
	public List<T> find(Map<String, Object> parms) {
		final Criteria criteria = createCriteria();
		if (parms == null) {
			return criteria.list();
		}

		Criterion fullSearch = null;
		for (Map.Entry<String, ? extends Object> entry : parms.entrySet()) {
			final Object value = entry.getValue();
			final Criterion criterion;
			if (value instanceof String) {
				criterion = Restrictions.like(entry.getKey(), (String) entry.getValue(), MatchMode.ANYWHERE);
			} else {
				criterion = Restrictions.eq(entry.getKey(), entry.getValue());
			}
			fullSearch = fullSearch == null ? criterion : Restrictions.or(fullSearch, criterion);
		}
		if (fullSearch != null) {
			criteria.add(fullSearch);
		}
		return criteria.list();
	}

	public List<T> find(String field, Object value) {
		final Map<String, Object> map = new HashMap<String, Object>();
		if (checkArg(field) && value != null) {
			map.put(field, value);
		}
		return find(map);
	}

	public void setReadOnly(boolean manual) {
		getSession().setFlushMode(manual ? FlushMode.MANUAL : FlushMode.AUTO);
	}

	private static boolean checkArg(String arg) {
		return arg != null && arg.trim().length() > 0;
	}

	public List<T> findIn(String field, List<Long> value) {
		final Criterion crit = Restrictions.in(field, value);
		return findByCriteria(crit);
	}

	private SessionFactory sessionFactory;

	public Session getSession() throws HibernateException {
		return sessionFactory.getCurrentSession();
	}

	public void closeSession() {
		sessionFactory.getCurrentSession().close();
	}

	public void beginTransaction() {
		closeSession();
		sessionFactory.getCurrentSession().beginTransaction();
	}

	public void commitTransaction() {
		sessionFactory.getCurrentSession().getTransaction().commit();
		closeSession();
	}

	public void rollbackTransaction() {
		sessionFactory.getCurrentSession().getTransaction().rollback();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

}
